package pl.edu.agh.intobl;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import pl.edu.agh.intobl.exceptions.IntOblException;

public class RouteSet {

	List<Route> routes = new LinkedList<Route>();

	public boolean addRoute(Route route) {
		if (route.size() > 0) {
			routes.add(route);
			return true;
		}

		return false;
	}

	public int size() {
		return routes.size();
	}

	public int demandsNumber() {
		int demands = 0;
		for (Route route : routes)
			demands += route.size();

		return demands;
	}

	public double fitness() {
		double fitness = 0.0;
		for (Route route : routes)
			fitness += route.length();

		return fitness;
	}

	public Route selectSubRoute(Random rand) {
		Route parentRoute = routes.get(rand.nextInt(routes.size()));
		int subRouteSize = rand.nextInt(parentRoute.size()) + 1;
		int subRouteStartIndex = rand.nextInt(parentRoute.size() - subRouteSize + 1);
		Route subRoute = parentRoute.getSubRoute(subRouteStartIndex, subRouteSize);

		return subRoute;
	}

	public Route removeSubRoute(Random rand) {
		Route parentRoute = routes.get(rand.nextInt(routes.size()));
		int subRouteSize = rand.nextInt(parentRoute.size()) + 1;
		int subRouteStartIndex = rand.nextInt(parentRoute.size() - subRouteSize + 1);
		Route subRoute = parentRoute.removeSubRoute(subRouteStartIndex, subRouteSize);

		return subRoute;
	}

	public RouteSet diff(Route subRoute) {
		RouteSet diffRouteSet = new RouteSet();
		for (Route route : routes)
			diffRouteSet.addRoute(route.diff(subRoute));

		return diffRouteSet;
	}

	public void insertAfter(int demanderNr, Route subRoute) throws IntOblException {
		for (Route route : routes) {
			if (route.contains(demanderNr)) {
				route.insertAfter(demanderNr, subRoute);
				break;
			}
		}
	}

	@Override
	public Object clone() {
		RouteSet clonedRouteSet = new RouteSet();
		for (Route route : routes)
			clonedRouteSet.addRoute((Route) route.clone());

		return clonedRouteSet;
	}

	public void swapDemand(Random rand) {
		routes.get(rand.nextInt(routes.size())).swapDemand(rand);
	}

	public void inverseRandomSubRoute(Random rand) {
		routes.get(rand.nextInt(routes.size())).inverseRandomSubRoute(rand);
	}

	public void moveRandomElement(Random rand) {
		boolean moveToNewRoute = rand.nextInt(2 * routes.size()) == 0 ? true : false;
		Demand demand = routes.get(rand.nextInt(routes.size())).removeRandomDemand(rand);

		if (moveToNewRoute) {
			Route newRoute = new Route();
			newRoute.addDemander(demand);
			addRoute(newRoute);
		} else {
			routes.get(rand.nextInt(routes.size())).insertAfterRandom(demand, rand);
		}
	}

	public void moveRandomSubRoute(Random rand) {
		boolean moveToNewRoute = rand.nextInt(2 * routes.size()) == 0 ? true : false;
		Route subRoute = removeSubRoute(rand);

		if (moveToNewRoute) {
			addRoute(subRoute);
		} else {
			routes.get(rand.nextInt(routes.size())).insertAfterRandom(subRoute, rand);
		}
	}

	@Override
	public String toString() {
		return "No.Vehicles=" + routes.size() + " routes:" + routes.toString();
	}

	public void validateRoutes() {
		RouteSet tailRouteSet = new RouteSet();
		Customer depot = ProblemSolver.customers.getDepot();
		for (Route route : routes) {
			if (route.capacity() > ProblemSolver.vehiclesCapacity) {
				tailRouteSet.routes.add(route.cutTail());
				tailRouteSet.validateRoutes();
			}

			/*
			 * TW restriction
			 * 
			 * Time constraints are not satisfied: in this case, three different
			 * types of violation may occur:
			 * 
			 * early arrival at a customer late arrival at a customer late
			 * arrival at the depot
			 * 
			 * The first situation is easily solved, since it's only required
			 * that the vehicle waits until it meets the earliest arrival time
			 * of the window. To resolve the other two cases, a new section is
			 * created on the itinerary, providing a valid route by adding a new
			 * vehicle to the solution.
			 */
			double currentTime = 0.0;
			LinkedList<Demand> demands = route.getDemands();
			Demand prev = null;
			boolean isSplitted = false;
			for (int i = 0; i < demands.size(); i++) {
				Demand next = demands.get(i);
				double distance = i == 0 ? ProblemSolver.customers.distanceMatrix[next.demander][0]
						: ProblemSolver.customers.distanceMatrix[next.demander][prev.demander];
				currentTime += distance;
				// ok
				if (next.readyTime <= currentTime && currentTime <= next.dueDate) {
					currentTime += next.serviceTime;
				}
				// early arrival
				else if (next.readyTime > currentTime) {
					currentTime = next.readyTime + next.serviceTime;
				}
				// late customer or late depot
				else if (currentTime > next.dueDate
						|| currentTime + next.serviceTime + ProblemSolver.customers.distanceMatrix[next.demander][0] > depot.dueDate) {
					tailRouteSet.routes.add(route.cutTail(i));
					isSplitted = true;
					break;
				}
				prev = next;
			}
			if (isSplitted) {
				tailRouteSet.validateRoutes();
			}
		}
		for (Route route : tailRouteSet.routes)
			routes.add(route);
	}
}
